Lisp 操作系统

#Innolight #Lisp

Lisp 操作系统(简称 LispOS)不仅仅是一个碰巧用 Lisp 编写的操作系统(尽管这本身就是一个很好的事情)。LispOS 也是一种操作系统,它以 Lisp 交互式环境为灵感,设计用户与系统之间以及应用程序与系统之间的接口。

在下面的内容中,我们将探讨 LispOS 可能包含的内容、它与现有操作系统有何不同,以及如何创建这样一个系统。

现有系统的问题

进程的概念

大多数流行的现有操作系统都源自于 20 世纪 70 年代编写的 Unix。Unix 所针对的计算机具有非常小的地址空间,小到不足以容纳大多数可用的终端用户应用程序。为了解决这个问题,Unix 的创造者引入了进程的概念。一个大型应用程序被编写成由几个较小的程序组成,每个程序都在自己的地址空间中运行。这些较小的程序通过一个程序将其输出流中的文本写入,供另一个程序读取来进行通信。这种通信方式被称为管道,而这一系列小型应用程序则被称为管道线。以生成排版文档的管道链为例(这是 Unix 设计的主要应用之一)。这个链包括一个用于创建表格的程序(称为 tbl)、一个用于生成图片的程序(称为 pic)、一个用于生成公式的程序(称为 eqn),当然还有一个排版程序本身(称为 troff)。

使用管道在应用程序的不同组件之间进行通信有以下几个缺点:

有趣的是,在大多数操作系统教科书中,进程的概念被呈现为操作系统设计的核心要素,而它应该被呈现为由于 20 世纪 70 年代现有小型计算机地址空间有限而产生的不幸的必要性。它还被呈现为获得某种安全性的唯一方法,防止一个应用程序故意或意外地修改另一个应用程序的数据。实际上,获得这种安全性的方法有多种,而单独的地址空间应该被视为一种缺点过多的方法。

如今,计算机的地址宽度为 64 位,这使得可以寻址近 20 艾字节的数据。为了了解这样一个数字的量级,考虑一个可以容纳 1 太字节的数据的相当大的磁盘。那么 2000 万个这样的磁盘可以直接被处理器寻址。因此,我们可以认为地址空间过小的问题已经得到解决。

层次化文件系统

现有的操作系统都带有层次化文件系统。有两个显著的问题,分别是层次化和文件。

层次化这个概念也可以追溯到 20 世纪 70 年代,当时它被认为是平面文件系统的一个巨大改进。然而,正如这篇文章清楚地解释那样,大多数事物本质上并不是层次化的。层次化的组织强加了名称之间的认为顺序。一个文档被称为 Lisp/Programs/2013/stuff、Programs/Lisp/2013/stuff、2013/Programs/Lisp/stuff 等等,通常并不重要。

文件的问题在于它只是一个没有结构的字节序列。这种缺乏结构的特点非常适合 Unix 管道模型,因为单个软件组件之间的中间步骤可以保存到文件中而不改变结果。但这也意味着,为了将复杂的数据结构存储在文件系统中,它们必须被转换为字节序列。而且,每当某个应用程序需要修改这样的结构时,它必须再次被解析并转换为内存中的结构。

主存储器和辅助存储器的区别

当前的系统(至少对于台式计算机来说)对主存储器和辅助存储器做出了非常明确的区别。不仅两者并不相同,而且它们的语义也完全不同:

这种区别以及两种存储器的语义,为大多数应用程序的用户带来了一个永久的困境:如果当前应用程序的数据没有保存,那么在电源丢失时它就会丢失;而如果已经保存,那么之前保存的数据将永远丢失。

早在 20 世纪 60 年代,就开发出了将主存储器和辅助存储器呈现为单一抽象的技术。例如,Multics 系统有一个单一的固定大小字节数组层次结构(称为段),它作为永久存储,但应用程序也可以将其视为任何内存数组。随着基于 Unix 的操作系统变得广泛流行,这些技术在很大程度上被遗忘了。

内核的概念

操作系统的内核是一个相当庞大、单一的程序,当计算机开机时启动。内核并不是计算机的普通程序。它以特权权限执行,因此可以直接访问设备以及必须受到保护、防止用户级程序直接使用的数据结构。

内核的存在本身就是一个问题,因为每当内核更新时,计算机就需要重启,然后所有现有的状态都会丢失,包括打开的文件和存储在易失性内存中的数据结构。有些程序,比如网络浏览器,通过记住打开的窗口以及每个窗口相关联的链接,来一定程度上弥补这个问题。

内核是单一的这一事实带来了问题,因为当需要以内核模式的形式向内核添加代码时,这样的代码可以完全访问整个计算机系统。这种普遍的访问权限当然代表了一种安全风险,但更常见的是,它可能会有缺陷,然后它通常会通过导致整个计算机崩溃而失败。

我们已经拥有解决这个问题的方案几十年了。例如,Multics 系统根本就没有内核。中断或系统调用由发出系统调用的用户级进程或在中断到达时正在执行的进程来执行。当时执行的代码并不是单一内核的一部分,而是作为独立的程序存在,可以在不重启系统的情况下添加或替换。当然,如果某些关键的系统范围的数据结构被破坏,系统仍然可能会崩溃,但大多数情况下,只有发出请求的用户级进程会崩溃。

单体应用程)

当前操作系统中的应用程序是用低级语言(如 C 或 C++)编写的。应用程序的构建使用了半个多世纪前的技术,其中源代码被编译成目标代码,然后链接以生成一个旨在其自己的专用地址空间中运行的可执行文件。

除了系统调用之外,应用程序中所有代码都在一个单一的地址空间中运行。再加上使用了低级语言,这种组织方式使应用程序容易受到病毒和其他安全相关攻击的侵害。典型的攻击利用了对缓冲区溢出的漏洞,这是由于编写应用程序所用的编程语言没有为数组插入边界检查,需要程序员用显式代码来完成,因此通常没有做到。

在这样的应用程序中,缓冲区溢出可以被利用来修改程序的执行,以便用应用程序编写者未打算执行的代码替换原本的应用程序代码。这种修改是可能的,因为执行栈是应用程序地址空间的一部分,而执行栈包含了稍后要执行的代码的地址,因此应用程序可以直接访问这些代码地址。

在 Lisp 操作系统中,栈对应用程序代码是不可访问的。因此,不可能改变栈上代表稍后要执行的代码的地址。此外,编程语言会自动检查数组的边界,因此不可能发生缓冲区溢出。

Lisp 操作系统中的应用程序并不是作为一个旨在其自己的地址空间中运行的单体可执行文件构建的。相反,应用程序由大量对象组成,这些对象的地址受到系统的保护,应用程序代码无法直接访问。因此,在这样的系统中,最常见的安全攻击技术是不可能实现的。

Lisp 操作系统的目标

Lisp 操作系统的三个主要目标对应于上一节中提到的现有系统的两个主要问题的解决方案。

单一地址空间

我们建议,与其让每个应用程序都有自己的地址空间,不如让所有应用程序共享一个单一的大型地址空间。这样,应用程序可以通过传递指针来共享数据,因为指针是全局有效的,与当前操作系统中的指针不同。

显然,如果所有应用程序共享一个单一的地址空间,就需要有一种不同的机制来确保它们之间的保护,以防止一个应用程序故意或意外地破坏另一个应用程序的数据。大多数高级编程语言(特别是 Lisp,但也有 Java 和许多其他语言)提出了一个解决方案,即简单地不允许用户执行任意及其代码。相反,它们只允许执行从语言的高级符号生成的代码,并且排除任意指针运算,以便应用程序只能访问自己的数据。这种技术有时被称为“可信编译器”。

有时可能希望用低级语言(如 C 或甚至汇编语言)编写应用程序,或者可能需要运行为其他系统编写的应用程序。这样的应用程序可以与正常的应用程序共存,但它们必须像当前操作系统一样在自己的地址空间中工作,并且在与其他应用程序通信时也会遇到同样的困难。

基于标签的对象存储

我们建议用一个对象存储来代替层次化文件系统,它可以包含任何对象。如果需要文件(即字节序列),它将被存储为一个字节数组。

而不是将对象组织成层次结构,存储中的对象可以选择性地与任意数量的标签相关联。这些标签时键/值对,例如,存储条目的创建日期、存档条目的创建者(一个用户)以及条目的访问权限。请注意,标签不是对象本身的属性,而是允许访问对象的存档条目的属性。有些标签可能从存储对象的内容中派生而来,例如电子邮件消息的发件人或日期。应该可以在不访问对象本身的情况下,仅通过标签完成大多数对存储的搜索。偶尔需要访问内容,例如当需要对文本内容进行原始搜索时。

有时希望将相关对象组合在一起,就像当前操作系统的目录一样。如果用户想要这样的组合,它只是存储中的另一个对象(比如类目录的实例)。无法适应非层次化组织的用户甚至可以将这样的目录存储为另一个目录中对象。

以下是一些可能的键/值对示例、它们的用途以及允许的值类型:

可能的值
类别 对象的性质,如电影音乐文章书籍用户手册字典课程讲座食谱程序银行对账单电子邮件。这些将从每个用户定义的可编辑集合中选择。
名称 与对象一起显示的字符串,如“A Dramatic Turn of Events”、“Three seasons”、“Alternative energy”。
作者 识别一个人、一个组织、一个公司等的对象。
类型 可用于电影、音乐专辑、程序、文章等。前卫金属科学算法垃圾回收游戏编程语言实现操作系统。这些将从每个用户定义的可编辑集合中选择。
格式 此标签可用于识别文档的文件类型,如 PDFogg/vorbisMPEG4PNG,在这种情况下,标签可以自动分配,也可以用于识别目录中文件的源格式,例如包含文章或用户手册的目录中的 LaTeXTexinfoHTML。这些将从每个用户定义的可编辑集合中选择。
创建日期 一个日期区间。
作曲家 用于音乐。代表一个人的对象。在合辑专辑中可以有多个这种类型的标签。
语言 代表自然语言(如英语越南语)或编程语言(如LispPython)的对象。这些将从每个用户定义的可编辑集合中选择。如果合适,文档可以有多个这样的标签,例如,如果某个程序使用多种编程语言,或者文档使用多种语言编写,如字典。
时长 可用于电影或音乐专辑等。代表时长的对象。
版本控制 可用于使用版本控制的任何文档,如程序等。GITSVNCVSdarks等。这些将从每个用户定义的可编辑集合中选择。

当(指向)一个对象作为对象存储搜索的结果返回给用户时,它实际上类似于操作系统文献中所说的“能力(capability)”。这种能力本质上只是一个带有几个位的指针,这些位表示用户对对象的访问权限。每个创建者可以根据自己的喜好解释这些位的内容,但通常它们会被用户来限制访问,例如,允许执行读取器方法,但不允许执行写入器方法。

单一存储器抽象

与两种不同的存储器抽象(主存储器和辅助存储器)不同,Lisp 操作系统将包含一个单一的抽象,它看起来就像任何交互式的 Lisp 系统,只是数据是永久性的。

由于数据是永久性的,应用程序编写者被鼓励提供一个复杂的撤销功能。

计算机的物理主存储器(半导体存储器)仅仅作为磁盘的缓存,因此对象的地址唯一地决定了它在磁盘上的存储位置。缓存像普通的虚拟存储器一样管理,使用现有的算法。

其他特性

防崩溃(也许)

在防崩溃系统方面有大量的工作,无论是操作系统还是数据库系统。在我们看来,这项工作令人困惑,因为没有明确说明。

有时目标被表述为希望在电源丢失时不会丢失数据。但这个问题的解决方案已经存在于每台笔记本电脑中;它简单地提供了一个电池,允许系统继续工作,或者以受控的方式关机。

其他时候,目标被表述为防止有缺陷的软件,以便数据可以定期存储(检查点)或许结合一个事务日志,以便在崩溃之前始终可以恢复系统的状态。但很难保护自己免受有缺陷的软件的侵害。检查点代码或事务日志代码可能存在缺陷,底层文件系统也可能存在缺陷。我们相信,开发人员的时间更好地用于查找和消除缺陷,而不是旨在因现有缺陷而恢复。

多个同时环境

为了允许用户在不影响其他用户的情况下向标准通用函数(如 print-object)添加方法,我们建议每个用户获得一个不同的全局环境。环境将名称映射到对象,如函数、类、类型、包等。不可变对象(如 common-lisp 包)可以同时存在于多个不同的环境中,但对象(如通用函数 print-object)在不同的环境中会有所不同。

多个环境还可以为用户提供更多的安全性,因为如果用户不小心移除了一些系统功能,那么可以从默认环境中恢复,而在最坏的情况下,可以为不小心破坏了大部分环境的用户安装一个新的默认环境。

最后,多个环境将简化对新功能的实验,而不会冒着破坏整个系统的风险。一个包的不同版本可以存在于不同的环境中。

如何实现

Lisp 操作系统最重要的方面不是所有代码都用 Lisp 编写,而是要在用户与系统之间以及应用程序与系统之间提供类似 Lisp 的接口。因此,利用现有的某些系统(可能是 Linux 或某种 BSD 版本)来提供诸如设备驱动程序、网络通信、线程调度等服务是合理的。

创建一个用作基础的 Lisp 系统

第一步是创建一个可以用作 Lisp 操作系统基础的 Common Lisp 系统。它应该已经允许有多个环境,并且应该在 64 位平台上可用。最好,这个系统尽可能少地使用 C 代码,并直接与底层内核的系统调用交互。

创建一个作为 Unix 进程的单用户系统

下一步是将 Common Lisp 系统转变为一个从用户和应用程序的 API 来说的操作系统。这个系统将包含对象存储,但可能没有访问控制功能。

完成这一步后,就可以编写或改编诸如文本编辑器、检查器、调试器、图形用户界面库等应用程序用于该系统。

创建设备驱动程序

最后一步是用新系统的本地设备驱动程序替换临时的 Unix 内核,并将系统转变为一个完整的多用户操作系统。

备注:翻译自Lisp Operating System